--- /dev/null
+/*
+
+ Support for Suunto Trackmanager SDF format,
+ Copyright (C) 2005-2006 Olaf Klein, o.b.klein@t-online.de
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
+*/
+
+/*
+ 2006/04/05: initial release (not published in GPSBbabel)
+ 2006/07/19: finished reader and writer for type 4,5,28 of ver. 1
+
+ ToDo: Ascending/Descending
+*/
+
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "defs.h"
+#include "csv_util.h"
+#include "strptime.h"
+#include "jeeps/gpsmath.h"
+#include "grtcirc.h"
+
+#include <time.h>
+
+#define MYNAME "stmsdf"
+
+#define ALT(a) (a->altitude != unknown_alt) ? a->altitude : 0
+
+typedef enum {
+ sdf_unknown,
+ sdf_header,
+ sdf_points,
+ sdf_custom
+} sdf_section_e;
+
+static gbfile *fin, *fout;
+
+typedef struct stmsdf_s {
+} stmsdf_t;
+
+static int lineno;
+static int datum;
+static int datum86;
+static int filetype;
+static route_head *route;
+static queue trackpts;
+static char *rte_name;
+static char *rte_desc;
+
+static waypoint *trkpt_out;
+static route_head *trk_out;
+
+static double trkpt_dist;
+static double minalt, maxalt, maxspeed;
+static double this_distance, all_dist;
+static time_t this_time, all_time;
+static double all_asc, all_desc;
+static int this_index; /* from 1 to ... */
+static int all_points;
+static int this_points;
+static int saved_points;
+static time_t start_time;
+static unsigned char this_valid;
+
+#define route_index this_index
+#define track_index this_index
+#define all_route_points all_points
+#define all_track_points all_points
+#define route_points this_points
+#define track_points this_points
+#define saved_track_points saved_points
+#define this_route_valid this_valid
+
+/* placeholders for options */
+
+static char *opt_route_index;
+static int opt_route_index_value;
+
+static
+arglist_t stmsdf_args[] = {
+ {"index", &opt_route_index, "Index of route (if more the one in source)",
+ "1", ARGTYPE_INT, "1", NULL },
+ ARG_TERMINATOR
+};
+
+
+/* ----------------------------------------------------------- */
+
+static void
+parse_header(char *line)
+{
+ char *str;
+ char *key = NULL;
+ char *prod = NULL;
+ int column = -1;
+
+ while ((str = csv_lineparse(line, "=", "", lineno)))
+ {
+ line = NULL;
+ column++;
+
+ switch(column) {
+ case 0:
+ key = xstrdup(str);
+ break;
+ case 1:
+ if (case_ignore_strcmp(key, "DATUM") == 0) datum = GPS_Lookup_Datum_Index(str);
+ else if (case_ignore_strcmp(key, "FILEVERSION") == 0) {
+ int ver = atoi(str);
+ is_fatal( (ver != 1),
+ MYNAME ": This version '%d' is not yet supported. Please report!", ver);
+ }
+ else if (case_ignore_strcmp(key, "NAME") == 0) rte_name = xstrdup(str);
+ else if (case_ignore_strcmp(key, "NOTES") == 0) /* ToDo */;
+ else if (case_ignore_strcmp(key, "SOURCE") == 0) rte_desc = xstrdup(str);
+ else if (case_ignore_strcmp(key, "TYPE") == 0) {
+ filetype = atoi(str);
+ switch(filetype) {
+ case 4: /* M9 TrackLog (Suunto Sail Manager) */
+ case 5: /* route */
+ case 28: /* X9 TrackLog (Suunto Trek Manager */
+ break;
+
+ case 78: prod = "S6 SkiChrono";
+ case 79: prod = "S6 Skilog";
+
+ default:
+ if (prod == NULL) prod = "unknown";
+ fatal(MYNAME ": Unsupported file type (%s, type %d)!\n", prod, filetype);
+ }
+ }
+ break;
+ }
+ }
+ if (key) xfree(key);
+
+}
+
+static int
+track_qsort_cb(const void *a, const void *b)
+{
+ const waypoint *wa = *(waypoint **)a;
+ const waypoint *wb = *(waypoint **)b;
+
+ return wa->creation_time - wb->creation_time;
+}
+
+static void
+finalize_tracks(void)
+{
+ waypoint **list;
+ int count = 0;
+ queue *elem, *tmp;
+ int index;
+ route_head *track = NULL;
+ int trackno = 0;
+
+ count = 0;
+ QUEUE_FOR_EACH(&trackpts, elem, tmp) { count++; };
+ if (count == 0) return;
+
+ list = (void *)xmalloc(count * sizeof(*list));
+
+ index = 0;
+ QUEUE_FOR_EACH(&trackpts, elem, tmp) {
+ list[index] = (waypoint *)elem;
+ dequeue(elem);
+ index++;
+ }
+
+ qsort(list, count, sizeof(*list), track_qsort_cb);
+
+ for (index = 0; index < count; index++) {
+ waypoint *wpt = list[index];
+ if (wpt->centiseconds == 2) { /* log continued */
+ track = NULL;
+ }
+ if (track == NULL) {
+ track = route_head_alloc();
+ track_add_head(track);
+ trackno++;
+ if (rte_name != NULL) {
+ if (trackno > 1)
+ xasprintf(&track->rte_name, "%s (%d)", rte_name, trackno);
+ else
+ track->rte_name = xstrdup(rte_name);
+ }
+ if (rte_desc != NULL)
+ track->rte_desc = xstrdup(rte_desc);
+ }
+ track_add_wpt(track, wpt);
+ if (wpt->centiseconds == 1) { /* log pause */
+ track = NULL;
+ }
+ wpt->centiseconds = 0;
+ }
+
+ xfree(list);
+}
+
+static void
+parse_point(char *line)
+{
+ char *str;
+ int column = -1;
+ int what = -1; /* -1 = unknown, 0 = tp, 1 = mp, 2 = wp, 3 = ap */
+ waypoint *wpt = NULL;
+ char *cx;
+ int hour, min, sec, day, month, year;
+
+ year = hour = -1;
+
+ while ((str = csv_lineparse(line, ",", "", lineno)))
+ {
+
+ line = NULL;
+ column++;
+
+ switch(column) {
+
+ case 0:
+ if (strcmp(str, "\"TP\"") == 0) {
+ what = 0;
+ column++; /* skip name */
+ }
+ else if (strcmp(str, "\"MP\"") == 0) what = 1;
+ else if (strcmp(str, "\"WP\"") == 0) what = 2;
+ else if (strcmp(str, "\"AP\"") == 0) what = 3;
+ else {
+ warning(MYNAME ": Unknown point type %s at line %d!\n", str, lineno);
+ return;
+ }
+ wpt = waypt_new();
+ break;
+
+ case 1:
+ wpt->shortname = csv_stringclean(str, "\"");
+ if ((what == 2) || (what == 3)) column += 2; /* doesn't have date and time */
+ break;
+ case 2:
+ sscanf(str, "%d.%d.%d", &day, &month, &year);
+ break;
+ case 3:
+ while ((cx = strchr(str, '.'))) *cx = ':';
+ sscanf(str, "%d:%d:%d", &hour, &min, &sec);
+ break;
+ case 4:
+ wpt->latitude = atof(str);
+ break;
+ case 5:
+ wpt->longitude = atof(str);
+ break;
+ case 6:
+ wpt->altitude = atof(str);
+ break;
+ case 7:
+ switch(what) {
+ case 0:
+ wpt->speed = atof(str) * 3.6; break;
+ case 3:
+ wpt->proximity = atof(str);
+ xasprintf(&wpt->notes, "Alarm point: radius=%s", str);
+ break;
+ }
+ break;
+ case 8:
+ if (what == 0) wpt->course = atof(str);
+ break;
+ case 9:
+ case 10:
+ break;
+ case 11:
+ if (what == 1) wpt->centiseconds = atoi(str); /* memory point type */
+ break;
+ }
+ }
+
+ if ((year > -1) && (hour > -1)) {
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+
+ tm.tm_year = year - 1900;
+ tm.tm_mon = month - 1;
+ tm.tm_mday = day;
+ tm.tm_hour = hour;
+ tm.tm_min = min;
+ tm.tm_sec = sec;
+
+ wpt->creation_time = mklocaltime(&tm);
+ }
+
+ if (datum != datum86) {
+ double ht;
+ GPS_Math_WGS84_To_Known_Datum_M(wpt->latitude, wpt->longitude, 0,
+ &wpt->latitude, &wpt->longitude, &ht, datum);
+ }
+
+ switch(what) {
+ case 0:
+ case 1:
+ ENQUEUE_TAIL(&trackpts, &wpt->Q);
+ break;
+ case 2:
+ case 3:
+ if (route == NULL) {
+ route = route_head_alloc();
+ route_add_head(route);
+ }
+ route_add_wpt(route, wpt);
+ break;
+ }
+}
+
+/* ----------------------------------------------------------- */
+
+static void
+rd_init(const char *fname)
+{
+ fin = gbfopen(fname, "r", MYNAME);
+
+ lineno = 0;
+ route = NULL;
+ datum = datum86 = GPS_Lookup_Datum_Index("WGS84");
+ filetype = 28;
+ rte_name = rte_desc = NULL;
+
+ QUEUE_INIT(&trackpts);
+}
+
+static void
+rd_deinit(void)
+{
+ gbfclose(fin);
+ if (rte_name) xfree(rte_name);
+ if (rte_desc) xfree(rte_desc);
+}
+
+static void
+data_read(void)
+{
+ char *buf;
+ sdf_section_e section = sdf_unknown;
+
+ while ((buf = gbfgetstr(fin)))
+ {
+ char *cin = lrtrim(buf);
+ lineno++;
+
+ if (*cin == '\0') continue;
+
+ if (*cin == '[')
+ {
+ char *cend = strchr(++cin, ']');
+
+ if (cend != NULL)
+ {
+ *cend = '\0';
+ cin = lrtrim(cin);
+ }
+ if ((*cin == '\0') || (cend == NULL))
+ fatal(MYNAME ": Invalid section header!\n");
+
+ if (case_ignore_strcmp(cin, "HEADER") == 0) section = sdf_header;
+ else if (case_ignore_strcmp(cin, "POINTS") == 0) section = sdf_points;
+ else if (case_ignore_strncmp(cin, "CUSTOM", 6) == 0) section = sdf_custom;
+ else {
+ warning(MYNAME ": Unknown section \"%s\". Please report.\n", cin);
+ section = sdf_unknown;
+ }
+ }
+ else switch(section)
+ {
+ case sdf_header:
+ parse_header(cin);
+ break;
+ case sdf_points:
+ parse_point(cin);
+ break;
+ case sdf_custom:
+ case sdf_unknown: break;
+ }
+ }
+ finalize_tracks(); /* memory points can be at the end of all trackpoints */
+}
+
+
+static void
+calculate(const waypoint *wpt, double *dist, double *speed, double *course,
+ double *asc, double *desc)
+{
+ if (trkpt_out != NULL) {
+
+ time_t time;
+
+ *course = 360 - DEG(heading(
+ RAD(trkpt_out->latitude), RAD(trkpt_out->longitude),
+ RAD(wpt->latitude), RAD(wpt->longitude)));
+ if (*course >= 360) *course -= 360;
+
+ *dist = radtometers(gcdist(
+ RAD(trkpt_out->latitude), RAD(trkpt_out->longitude),
+ RAD(wpt->latitude), RAD(wpt->longitude)));
+ if (*dist < 0.1) *dist = 0; /* calc. diffs on 32- and 64-bit hosts */
+
+ time = wpt->creation_time - trkpt_out->creation_time;
+ if (time == 0)
+ *speed = 0;
+ else
+ *speed = *dist / (double)time;
+
+ if (asc && desc && (trkpt_out->altitude != unknown_alt) && (wpt->altitude != unknown_alt)) {
+ double dh = wpt->altitude - trkpt_out->altitude;
+ if (dh > 0)
+ *asc += dh;
+ else
+ *desc -= dh;
+ }
+
+ }
+ else {
+ *speed = 0;
+ *dist = 0;
+ *course = 0;
+ if (asc) *asc = 0;
+ if (desc) *desc = 0;
+ }
+ if (wpt->speed != unknown_speed) *speed = wpt->speed / 3.6; /* -> meters per second */
+ if (wpt->course != unknown_course) *course = wpt->course;
+}
+
+/* pre-calculation callbacks */
+
+static void
+any_hdr_calc_cb(const route_head *trk)
+{
+
+ trkpt_out = NULL;
+ this_distance = 0;
+ this_time = 0;
+ this_points = 0;
+
+ this_index++;
+ this_valid = ((opt_route_index_value < 1) || (opt_route_index_value == this_index));
+ if (! this_valid) return;
+
+ if (!rte_name && trk->rte_name) {
+ rte_name = trk->rte_name;
+ rte_desc = trk->rte_desc;
+ }
+
+ trk_out = (route_head *)trk;
+}
+
+static void
+any_waypt_calc_cb(const waypoint *wpt)
+{
+ double speed, course, dist;
+
+ /* we can only write ONE route */
+ if (! this_valid) return;
+
+ if ((all_points == 0) && (this_points == 0)) start_time = wpt->creation_time;
+
+ this_points++;
+
+ if ((wpt->altitude != unknown_alt) && (wpt->altitude < minalt)) minalt = wpt->altitude;
+ if ((wpt->altitude != unknown_alt) && (wpt->altitude > maxalt)) maxalt = wpt->altitude;
+ calculate(wpt, &dist, &speed, &course, &all_asc, &all_desc);
+ if (speed > maxspeed) maxspeed = speed;
+
+ this_distance = this_distance + dist;
+ if (trkpt_out != NULL)
+ this_time += (wpt->creation_time - trkpt_out->creation_time);
+
+ trkpt_out = (waypoint *)wpt;
+}
+
+static void
+any_tlr_calc_cb(const route_head *trk)
+{
+ if (! this_valid) return;
+
+ all_dist += this_distance;
+ all_time += this_time;
+ all_points += this_points;
+}
+
+/* write callbacks */
+
+static void
+track_disp_hdr_cb(const route_head *trk)
+{
+ track_index++;
+ track_points = 0;
+ trk_out = (route_head *)trk;
+ trkpt_out = NULL;
+}
+
+
+static void
+track_disp_wpt_cb(const waypoint *wpt)
+{
+ struct tm tm;
+ char tbuf[32];
+ double course, speed, dist;
+ int flag = 0;
+
+ track_points++;
+ all_track_points++;
+
+ tm = *localtime(&wpt->creation_time);
+ strftime(tbuf, sizeof(tbuf), "%d.%m.%Y,%H:%M.%S", &tm);
+
+ calculate(wpt, &dist, &speed, &course, NULL, NULL);
+ trkpt_dist = trkpt_dist + dist;
+
+ if (track_points == trk_out->rte_waypt_ct) { /* I'm the last in that list */
+ if (all_track_points != saved_track_points) { /* but not the overall latest */
+ flag = 1;
+ }
+ }
+ else if (track_points == 1) { /* I'm the first in that list */
+ if (all_track_points > 1) { /* but not the first ever seen */
+ flag = 2;
+ }
+ }
+
+ if (flag == 1) {
+ char *name = wpt->shortname;
+ if (name == NULL) name = "Log paused";
+ gbfprintf(fout, "\"MP\",\"%s\"", name);
+ }
+ else if (flag == 2) {
+ char *name = wpt->shortname;
+ if (name == NULL) name = "Log continued";
+ gbfprintf(fout, "\"MP\",\"%s\"", name);
+ }
+ else
+ gbfprintf(fout, "\"TP\"");
+
+ gbfprintf(fout, ",%s,%.6lf,%.6lf,%.f,%.2f",
+ tbuf,
+ wpt->latitude, wpt->longitude, ALT(wpt), speed);
+ if (flag)
+ gbfprintf(fout, ",0,0,%d", flag); /* press, temperature, memory point type */
+ else
+ gbfprintf(fout, ",%.1f", course);
+
+ if (trkpt_dist != 0)
+ gbfprintf(fout, ",%.6f\n", trkpt_dist);
+ else
+ gbfprintf(fout, ",0\n");
+
+ trkpt_out = (waypoint *)wpt;
+}
+
+static void
+track_disp_tlr_cb(const route_head *rte)
+{
+ trkpt_out = NULL;
+}
+
+static void
+route_disp_hdr_cb(const route_head *rte)
+{
+ route_index++;
+ this_route_valid = ((opt_route_index_value < 1) || (opt_route_index_value == track_index));
+}
+
+static void
+route_disp_wpt_cb(const waypoint *wpt)
+{
+ if (this_route_valid) {
+ gbfprintf(fout, "\"WP\",\"%s\",%.8lf,%.8lf,%.f\n",
+ wpt->shortname, wpt->latitude, wpt->longitude, ALT(wpt));
+ }
+}
+
+static void
+track_disp_custom_cb(const waypoint *wpt)
+{
+ if (wpt->creation_time && (wpt->altitude != unknown_alt)) {
+ gbfprintf(fout, "%lu,%.f\n", wpt->creation_time - start_time, wpt->altitude);
+ }
+}
+
+static void
+wr_init(const char *fname)
+{
+ fout = gbfopen(fname, "w", MYNAME);
+}
+
+static void
+wr_deinit(void)
+{
+ gbfclose(fout);
+}
+
+static void
+data_write(void)
+{
+ gbfprintf(fout, "[HEADER]\n");
+ gbfprintf(fout, "FILEVERSION=1\n");
+ gbfprintf(fout, "SOURCE=FILE\n");
+ gbfprintf(fout, "DATUM=WGS84\n");
+
+ rte_name = NULL;
+ rte_desc = NULL;
+ trkpt_out = NULL;
+ opt_route_index_value = -1; /* take all tracks from data pool */
+ track_index = 0;
+ minalt = -unknown_alt;
+ maxalt = unknown_alt;
+ maxspeed = 0;
+ all_dist = 0;
+ all_time = 0;
+ all_asc = 0;
+ all_desc = 0;
+ all_points = 0;
+ start_time = 0;
+
+
+ switch(global_opts.objective)
+ {
+ case rtedata:
+ gbfprintf(fout, "TYPE=5\n");
+
+ opt_route_index_value = atoi(opt_route_index);
+ route_disp_all(any_hdr_calc_cb, any_tlr_calc_cb, any_waypt_calc_cb);
+ gbfprintf(fout, "DISTANCE=%.f\n", all_dist);
+ if (rte_name) gbfprintf(fout, "NAME=%s\n", rte_name);
+ gbfprintf(fout, "[POINTS]\n");
+ if (route_points > 0) {
+ track_index = 0;
+ route_disp_all(route_disp_hdr_cb, NULL, route_disp_wpt_cb);
+ }
+ break;
+
+ case wptdata:
+ case trkdata:
+ gbfprintf(fout, "TYPE=28\n");
+
+ track_disp_all(any_hdr_calc_cb, any_tlr_calc_cb, any_waypt_calc_cb);
+ if (all_track_points > 0) {
+ if (rte_name) gbfprintf(fout, "NAME=%s\n", rte_name);
+ if (minalt != -unknown_alt) gbfprintf(fout, "MINALT=%.f\n", minalt);
+ if (maxalt != unknown_alt) gbfprintf(fout, "MAXALT=%.f\n", maxalt);
+ gbfprintf(fout, "MAXSPEED=%.2f\n", maxspeed);
+ gbfprintf(fout, "DISTANCE=%.f\n", all_dist);
+ gbfprintf(fout, "DURATION=%lu\n", all_time);
+// gbfprintf(fout, "TOTASC=%.f\n", all_asc);
+// gbfprintf(fout, "TOTDSC=%.f\n", all_desc);
+ if (start_time) {
+ struct tm tm;
+ char tbuf[32];
+
+ tm = *localtime(&start_time);
+ strftime(tbuf, sizeof(tbuf), "%d.%m.%Y %H:%M.%S", &tm);
+ gbfprintf(fout, "DATE=%s\n", tbuf);
+ }
+ if (all_time) gbfprintf(fout, "AVGSPEED=%.2f\n", all_dist / (double)all_time);
+ }
+ gbfprintf(fout, "[POINTS]\n");
+ if (all_track_points > 0) {
+
+ trkpt_dist = 0;
+ saved_track_points = all_track_points;
+ all_track_points = 0;
+ track_disp_all(track_disp_hdr_cb, track_disp_tlr_cb, track_disp_wpt_cb);
+
+ if (start_time) {
+ gbfprintf(fout, "[CUSTOM1]\n");
+ track_index = 0;
+ track_disp_all(NULL, NULL, track_disp_custom_cb);
+ }
+ }
+ break;
+ }
+}
+
+/* ------------------------------------------------------------------ */
+
+ff_vecs_t stmsdf_vecs = {
+ ff_type_file,
+ { ff_cap_none,
+ ff_cap_read | ff_cap_write,
+ ff_cap_read | ff_cap_write },
+ rd_init,
+ wr_init,
+ rd_deinit,
+ wr_deinit,
+ data_read,
+ data_write,
+ NULL,
+ stmsdf_args,
+ CET_CHARSET_MS_ANSI, 0 /* CET-REVIEW */
+};
+
+/* ================================================================== */